from flask_restful import Resource
from comment.models.user import User
from flask import current_app,g
from flask import request
from comment.utils.yanzhengma.example.SendMessage import send_message
from comment.utils.limiter import limiter as lmt
from . import constants
from flask_limiter.util import get_remote_address
from comment.utils.shopping_redis import redis_clint#用于redis客户端的连接 用于把手机验证码放入redis数据库中存放
from flask_restful.reqparse import RequestParser#做请求参数验证的
from comment.utils import parser
from comment.models import db
from comment.utils.tokens_pyjwt import generate_tokens, verify_tokens  # generate_tokens这个函数就是用来生成token的
from comment.utils.decorators import login_required
#定义一个测试的资源类
class Shopping_User(Resource):

    """
    在get函数上加上登陆的拦截  method_decorators这个属性是固定写法 是在flask_restful中的
    login_required表示的是定义了一个装饰器
    """
    method_decorators ={
        'get':[login_required],
        'post':[login_required]

    }
    """
    这⼀句以后，函数执⾏到API下的具体HTTP⽅法之前会先执⾏装饰器操作， 并且执⾏装饰器操作时可以调⽤所有传给API的参数，并对其进⾏操
    作
            """
    def get(self):#测试 当前get函数必须让用户登陆之后才能访问
        current_app.logger.info('我的测试类日志')#这里的info表示的是info级别
        return {'hello':'测试'}
    def post(self):
        return {'hello':'post测试'}

    def put(self):
        return {'hello': 'post测试'}


#这是一个发送资源的资源类（restful编程 返回给前端的是json格式的资源）
class User_SMS(Resource):
    '''
    发送验证码信息
    '''
    error_message='to many message'
    #这里就是两个限制器加到资源里面去了
    # decorators =[
    #     #三个参数：参数1：限流的 速率 参数2：key_func(限制的条件)  参数三： 如果超出限制后的提示信息
    #     lmt.limiter(constants.LIMIT_SMS_CODE_BY_MOBILE,key_func=lambda :request.args['phone'],
    #                 error_message=error_message),
    #     lmt.limiter(constants.LIMIT_SMS_CODE_BY_IP, key_func=get_remote_address(),
    #                 error_message=error_message),
    # ]
    # 这里就是两个限制器加到资源里面去了  用于发短信的时候
    decorators = [
        # 三个参数：参数1：限流的速率,参数2：key_func,参数3：如果超出限制之后的提示信息
        lmt.limit(constants.LIMIT_SMS_CODE_BY_MOBILE,
                  key_func=lambda: request.args['phone'],
                  error_message=error_message),
        lmt.limit(constants.LIMIT_SMS_CODE_BY_IP,
                  key_func=get_remote_address,
                  error_message=error_message)
    ]
    def get(self):
        phone=request.args.get('phone').strip()
        random_num=send_message(phone)#验证码为random_num

        #短息验证码发送成功后，还需要把redis数据库中 以便下次验证  验证码的时效性为5分钟（使用 setex函数）
        redis_clint.setex('shopping:code:{}'.format(phone),constants.SMS_CODE_EXPIRES,random_num)#传3个参数 key  参数2：时间时效
        return {'号码': phone,'验证码：':random_num}

    # '''
    #     发送手机短信，验证手机号码
    #     '''
    #
    # def get(self):  # get请求的参数往往是参数名字+参数
    #     phone = request.args.get('phone')
    #     random_num = send_message(phone)  # 验证码为random_num
    #     if random_num:  # 如果有验证码返回 则打印下面的日志
    #         current_app.logger.info('给手机号为{}，发送短信成功'.format(phone))  # 从app中得到专门获取日志的对象logger info表示什么级别的
    #         # 验证码需要存起来（用于验证用户输入的验证码是否正确） 存在缓存数据库redis中
    #         # 需要准备好redis数据库的连接
    #         # setex有三个参数 1.key代表键（redis存放这一条数据的键） 2.有效时间 3.具体的值
    #         redis_clint.setex('registerCode:{}'.format(phone), constants.SMS_CODE_EXPIRES,
    #                  random_num)  # setex 就是把验证码按照字节数据的格式存到redis数据库中（有时效性的字节）
    #         # 上面registerCode:{}是键 random_num是值
    #         return {'msg': 'success', 'smsCode': random_num}  # smsCode表示返回给前端
    #     else:
    #         # 短信没发成功
    #         return {'message': '此手机号{}发送短信失败，请更换手机号码'.format(phone), 'code': 20002}




#这个资源类用于验证输入的验证码是否正确
class AuthorizationCodeResoruce(Resource):

    """
    提交手机号和验证码，开始验证
    """
    def post(self):
        rp=RequestParser()#对请求的参数验证
        rp.add_argument('phone',type=parser.mobile,location='form',required=True)#require表示必须要验证
        rp.add_argument('code',type=parser.regex(r'^\d{4}$'),location='form',required=True)
        args=rp.parse_args()#验证通过就会得到参数args
        phone=args.phone
        random_num=args.code

        #校验这个验证码 random_num
        #从redis数据库中得到之前保存的验证码
        key='shopping:code:{}'.format(phone)
        try:#有可能数据库连不上 进行捕获异常
            real_code=redis_clint.get(key)#从redis返回的是字节数据
        except ConnectionError as e:#通过日志将错误打印出来
            current_app.logger.error(e)#error表示日志的级别
            return {'message':'redis db connect error'},400#状态为400

        #开始校验
        if not real_code or real_code.decode()!=random_num:
            return {'message':'Invalid code.'},400#验证不通过
        return {'phone':phone,'msg':'code success.'}




class RegisterUserResource(Resource):
    """
    填写账号信息 完成用户注册
    """
    def post(self):
        rp = RequestParser()  # 对请求的参数验证
        rp.add_argument('phone', type=parser.mobile, required=True)  # require表示必须要验证
        rp.add_argument('username',  required=True)
        rp.add_argument('password',  required=True)
        rp.add_argument('email',  type=parser.email,required=True)
        args=rp.parse_args()
        username=args.username
        password=args.password
        phone=args.phone
        email=args.email

        #验证用户名是否是唯一的：先从mysql数据库中根据当前用户名查询
        u=User.query.filter(User.username==username).first()
        if u:#表示该用户已经存在
            current_app.logger.info('{}：用户名已经存在 请换一个'.format(username))#将日志打印出来
            return {'message':'The username already exists'},400

        #没查到 把用户信息保存到数据库中
        u=User(username=username,pwd=password,email=email,phone=phone,status=0)
        db.session.add(u)
        db.session.commit() 
        return {'msg':'ok'}



class UserLoginResource(Resource):
    """
    登陆
    """
    def post(self):
        username=request.form.get('username')
        password=request.form.get('password')
        if not all([username,password]):#all用于判断多个遍量是否为空
            return {'message':'数据不完整'},400
        user=User.query.filter(User.username==username).first()
        if user:
            if user.check_password(password):
                #必须把登陆成功之后的用户id先得到token，token再返回给前端
                token=generate_tokens(user.id)#获得token  generate_tokens获得token的函数 就是加密
                #一开始时在进行登陆拦截（也就是在 函数执⾏到API下的具体HTTP⽅法之前会先执⾏装饰器操作， 并且执⾏装饰器操作时可以调⽤所有传给API的参数，并对其进⾏操作时）
                #在这个项目上也就是说要如果想要查看订单信息 就得先查看查看是否是登陆的 如果时登陆的才能进行后续操作 这里就是通过获取请求头中是否含有token进行判断
                current_app.logger.info(verify_tokens(token))#将结果通过日志打印
                return {'msg':'Login Success.','token':token,'username':username},200#token 就是一个字符串
        return {'message':'用户名或者密码错误'},400#给的是状态码400



class UserLoginOutResource(Resource):
    """
    退出登陆
    """
    def post(self):
        #登陆的信息是保存在全局变量g中的 登陆成功后一定会有一个token 退出登陆就是把g的全局变量删掉就行了
        if g.user_id:
            g.user_id=None
        return {'msg':'退出登陆'}




class IsExisPhoneResource(Resource):
    """
    判断手机号是否存在
    """
    def post(self):
        phone=request.form.get('phone')
        # phone=request.form('phone')
        user=User.query.filter(User.phone==phone).first()
        if user:#如果用户存在，则表示手机号存在
            return {'isExist':True,'message':'此手机号码已经注册','code':203}
        return {'msg':'手机号可以注册'}